package com.core.convenientCode;

import com.common.utils.Constants;
import com.core.convenientCode.common.VelocityUtils;
import com.model.pojo.sys.SysGenTable;
import com.model.pojo.sys.SysGenTableColumn;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author: zql
 * @Description:自动生成代码服务
 * @Data: 11:33 2021/6/2
 */
@Slf4j
@Service
public class ConvenientCodeService {

    public static final byte TO_DIR_MOD_OVERWRITE = 0;
    public static final byte TO_DIR_MOD_APPEND = 1;
    public static final byte TO_DIR_MOD_NEW = 2;

    @Resource
    SysGenTableService sysGenTableService;

    @Resource
    SysGenTableColumnService sysGenTableColumnService;

    /**
     * @Author zql
     * @Description // 生成代码，打包成zip压缩文件
     * @Date 14:48 2021/8/24
     * @Params tableName
     *		   zip
     * @return void
     **/
    public void convenientCodeToZip(String tableName, ZipOutputStream zip) {
        getCodeZip(convenientCode(tableName), zip);
    }

    /**
     * @Author zql
     * @Description // 生成代码，放在指定路径。不允许轮询创建目录，当文件已存在时，将旧文件标记为.old，并新建文件。
     * @Date 17:14 2021/8/24
     * @Params tableName 数据库表名
     *		   dir 代码存放目录
     * @return void
     **/
    public void convenientCodeToDir(String tableName, String dir) {
        getCodeToDir(convenientCode(tableName), dir, TO_DIR_MOD_NEW, false);
    }

    /**
     * @Author: zql
     * @Description:
     * @param tableName
     * @Data: 13:17 2021/11/19
     */
    private Map<String, String> convenientCode(String tableName) {
        initVelocity();

        //获取表
        SysGenTable sysGenTable = sysGenTableService.getSysGenTable(tableName);
        //获取字段列表
        List<SysGenTableColumn> sysGenTableColumnList = sysGenTableColumnService.getTableColumnList(sysGenTable.getSysGenTableId());

        //定义字段信息
        VelocityContext context = VelocityUtils.prepareContext(sysGenTable,sysGenTableColumnList);

        //获取模板路径
        List<String> templateList=VelocityUtils.getTemplateList();

        Map<String, String> codeFiles = new HashMap<>(templateList.size());
        String filePathName;
        String fileContent;

        for(String template:templateList) {

            //创建接收对象
            StringWriter sw = new StringWriter();

            //根据模板和参数生成新文件
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            filePathName = VelocityUtils.getFilePathName(template, sysGenTable);
            fileContent = sw.toString();
            codeFiles.put(filePathName, fileContent);
            IOUtils.closeQuietly(sw);
        }

        return codeFiles;
    }

    /**
     * @param
     * @Author: zql
     * @Description: 打包成zip文件
     * @Data: 14:22 2021/6/2
     */
    private void getCodeZip(Map<String, String> codeFiles, ZipOutputStream zip) {

        for(String fileName : codeFiles.keySet()) {
            try
            {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(fileName));
                IOUtils.write(codeFiles.get(fileName), zip, Constants.UTF8);
                zip.flush();
                zip.closeEntry();
            }
            catch (IOException e)
            {
                log.error("渲染模板失败，文件名：" + fileName, e);
            }
        }
    }

    /**
     * @Author zql
     * @Description //将生成的代码写入目标目录
     * @Date 11:15 2021/8/25
     * @Params codeFiles 代码文件
     *		   dir  目标目录
     *		   mode 写入模式，0：覆写；1：追加：2、新建
     *		   allowMkdirs 是否允许轮询创建目录（一般为否，为方便测试设置为是）
     * @return void
     **/
    private void getCodeToDir(Map<String, String> codeFiles, String dir, byte mode, boolean allowMkdirs) {
        StringBuilder sb = new StringBuilder();
        for(String fileName : codeFiles.keySet()) {
            sb.delete(0, sb.length());
            sb.append(dir).append(fileName);
            try
            {
                createFile(sb.toString(), codeFiles.get(fileName), mode, allowMkdirs);
            }
            catch (IOException e)
            {
                log.error("渲染模板失败，文件名：" + fileName, e);
            }
        }
    }

    /**
     * @Author zql
     * @Description //TODO
     * @Date 8:39 2021/8/25
     * @Params fileName 文件名
     *		   fileContent 文件内容
     *		   mode 创建模式，当文件存在时，SAVE：保存原文件为.old；APPEND：在原文件中追加；OVERWIRTE：覆盖原文件
     *		   allowMkdirs
     * @return void
     **/
    private void createFile(String fileName, String fileContent, byte mode, boolean allowMkdirs) throws IOException {

        File file = new File(fileName);

        // 创建目录。允许为文件创建上一级目录，根据allowMkdirs参数决定是否允许递归创建目录
        File fileDir = file.getParentFile();
        if(!fileDir.exists()) {
            if(allowMkdirs) {
                fileDir.mkdirs();
            } else {
                fileDir.mkdir();
            }
        }

        // 写文件。当文件不存在时，直接创建文件并写文件；当文件已存在时，根据mode参数写文件。
        BufferedWriter bufferedWriter;
        if(file.exists()) {
            if(TO_DIR_MOD_NEW == mode) {

                //save模式。重命名原文件为.old文件，新建文件并写。
                file.renameTo(new File(fileName + ".old"+System.currentTimeMillis()));
                file = new File(fileName);
                bufferedWriter = new BufferedWriter(new FileWriter(file.getAbsoluteFile()));

            } else if(TO_DIR_MOD_APPEND == mode) {

                // 追加模式。在原文件中追加，用注释分开新代码与原代码。
                bufferedWriter = new BufferedWriter(new FileWriter(file.getAbsoluteFile(), true));
                bufferedWriter
                        .append("\n//====================================")
                        .append("\n//========  以下是自动生成的代码  ========")
                        .append("\n//====================================\n");

            } else if(TO_DIR_MOD_OVERWRITE == mode) {
                // 覆写模式。直接覆盖原文件。
                bufferedWriter = new BufferedWriter(new FileWriter(file.getAbsoluteFile()));

            } else {
                // 未识别的模式，不写
                log.warn("未创建"+fileName);
                return ;
            }
        } else {
            file.createNewFile();
            bufferedWriter = new BufferedWriter(new FileWriter(file.getAbsoluteFile()));
        }

        bufferedWriter.append(fileContent);
        bufferedWriter.flush();
    }

    private static void initVelocity() {
        Properties p = new Properties();
        try {
            // 加载classpath目录下的vm文件
            p.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            // 定义字符集
            p.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8");
            p.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");
            // 初始化Velocity引擎，指定配置Properties
            Velocity.init(p);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
